package se.prb.cheesymock;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * A utility state object that implments the {@link CheesyCallback} interface in
 * order to keep track of all method calls.
 *
 * @author björn.ekryd
 */
public class CheesyMethodStateObject implements CheesyCallback {
	private final Map<Method, StateAndArguments> methodCalls = new TreeMap<Method, StateAndArguments>(
			new Comparator<Method>() {
				public int compare(Method o1, Method o2) {
					int compareTo = o1.getName().compareTo(o2.getName());
					if (compareTo != 0) {
						return compareTo;
					}
					Class<?>[] parameterTypes1 = o1.getParameterTypes();
					Class<?>[] parameterTypes2 = o2.getParameterTypes();
					compareTo = parameterTypes1.length - parameterTypes2.length;
					if (compareTo != 0) {
						return compareTo;
					}
					for (int i = 0; i < parameterTypes1.length; i++) {
						compareTo = parameterTypes1[i].getName().compareTo(parameterTypes2[i].getName());
						if (compareTo != 0) {
							return compareTo;
						}
					}
					return 0;
				}
			});

	public void clearAll() {
		methodCalls.clear();
	}

	public boolean containsMethod(Method method) {
		return methodCalls.containsKey(method);
	}

	public boolean containsMethod(String methodName) {
		return getByMethodName(methodName) != null;
	}

	public List<Object[]> getArguments(Method method) {
		StateAndArguments stateAndArguments = methodCalls.get(method);
		if (stateAndArguments == null) {
			return null;
		}
		return stateAndArguments.getArguments();
	}

	public List<Object[]> getArguments(String methodName) {
		StateAndArguments stateAndArguments = getByMethodName(methodName);
		if (stateAndArguments == null) {
			return null;
		}
		return stateAndArguments.getArguments();
	}

	public int getState(Method method) {
		StateAndArguments stateAndArguments = methodCalls.get(method);
		if (stateAndArguments == null) {
			return 0;
		}
		return stateAndArguments.getState();
	}

	public int getState(String methodName) {
		StateAndArguments stateAndArguments = getByMethodName(methodName);
		if (stateAndArguments == null) {
			return 0;
		}
		return stateAndArguments.getState();
	}

	public void invoke(Method method, Object[] arguments) {
		StateAndArguments stateAndArguments = methodCalls.get(method);
		if (stateAndArguments != null) {
			stateAndArguments.increment(arguments);
			return;
		}
		methodCalls.put(method, new StateAndArguments(arguments));
	}

	private StateAndArguments getByMethodName(String methodName) {
		StateAndArguments returnValue = null;
		Set<Method> keySet = methodCalls.keySet();
		for (Method method : keySet) {
			if (method.getName().equals(methodName)) {
				if (returnValue != null) {
					throw new IllegalStateException("More that one match for methodname: " + methodName);
				}
				returnValue = methodCalls.get(method);
			}
		}
		return returnValue;
	}
}

final class StateAndArguments {
	private int state = 1;
	private final List<Object[]> arguments = new ArrayList<Object[]>();

	public StateAndArguments(Object[] arguments) {
		this.arguments.add(arguments);
	}

	public void increment(Object[] arguments) {
		state++;
		this.arguments.add(arguments);
	}

	public int getState() {
		return state;
	}

	public List<Object[]> getArguments() {
		return arguments;
	}
}
